#pragma once

#include "zrpc/net/socket.h"
#include <vector>
#include <algorithm>
#include <assert.h>
#include <string_view>
#include <string>
#include <errno.h>

namespace zrpc {
class Buffer {
 public:
  static const int32_t kCheapPrepend = 8;
  static const int32_t kInitialSize = 1024;

  explicit Buffer(int32_t initialsize = kInitialSize)
      : buffer_(kCheapPrepend + initialsize),
        reader_index_(kCheapPrepend),
        writer_index_(kCheapPrepend) {
    assert(ReadableBytes() == 0);
    assert(WritableBytes() == initialsize);
    assert(PrependableBytes() == kCheapPrepend);
  }

  void Swap(Buffer &rhs) {
    buffer_.swap(rhs.buffer_);
    std::swap(reader_index_, rhs.reader_index_);
    std::swap(writer_index_, rhs.writer_index_);
  }

  int32_t ReadableBytes() const { return writer_index_ - reader_index_; }
  int32_t WritableBytes() const { return buffer_.size() - writer_index_; }
  int32_t GetWriterIndex() { return writer_index_; }
  int32_t PrependableBytes() const { return reader_index_; }

  const char *Peek() const { return Begin() + reader_index_; }
  const char *Start() { return Begin() + kCheapPrepend; }
  char *Data() { return Begin() + reader_index_; }

  const char *FindCRLF() const {
    const char *crlf = std::search(Peek(), BeginWrite(), kCRLF, kCRLF + 2);
    return crlf == BeginWrite() ? nullptr : crlf;
  }

  const char *FindCRLF(const char *start) const {
    assert(Peek() <= start);
    assert(start <= BeginWrite());
    const char *crlf = std::search(start, BeginWrite(), kCRLF, kCRLF + 2);
    return crlf == BeginWrite() ? nullptr : crlf;
  }

  const char *FindEOL() const {
    const void *eol = memchr(Peek(), '\n', ReadableBytes());
    return static_cast<const char *>(eol);
  }

  const char *FindCONTENT() const {
    const char *content =
        std::search(Peek(), BeginWrite(), CONTENT, CONTENT + 14);
    return content == BeginWrite() ? nullptr : content;
  }

  const char *FindEOL(const char *start) const {
    assert(Peek() <= start);
    assert(start <= BeginWrite());
    const void *eol = memchr(start, '\n', BeginWrite() - start);
    return static_cast<const char *>(eol);
  }

  void UnRetrieveInt64() { UnRetrieve(sizeof(int64_t)); }
  void UnRetrieveInt32() { UnRetrieve(sizeof(int32_t)); }
  void UnRetrieveInt16() { UnRetrieve(sizeof(int16_t)); }
  void UnRetrieveInt8() { UnRetrieve(sizeof(int8_t)); }
  void UnRetrieve(int32_t len) {
    assert(len <= WritableBytes());
    if (len < WritableBytes()) {
      reader_index_ -= len;
    } else {
      RetrieveAll();
    }
  }

  void Retrieve(int32_t len) {
    assert(len <= ReadableBytes());
    if (len < ReadableBytes()) {
      reader_index_ += len;
    } else {
      RetrieveAll();
    }
  }

  void RetrieveUntil(const char *end) {
    assert(Peek() <= end);
    assert(end <= BeginWrite());
    Retrieve(end - Peek());
  }

  void RetrieveInt64() { Retrieve(sizeof(int64_t)); }
  void RetrieveInt32() { Retrieve(sizeof(int32_t)); }
  void RetrieveInt16() { Retrieve(sizeof(int16_t)); }
  void RetrieveInt8() { Retrieve(sizeof(int8_t)); }

  void RetrieveAll() {
    reader_index_ = kCheapPrepend;
    writer_index_ = kCheapPrepend;
  }

  std::string RetrieveAsString(int32_t len) {
    assert(len <= ReadableBytes());
    std::string result(Peek(), len);
    Retrieve(len);
    return result;
  } 

  std::string RetrieveAllAsString() {
    return RetrieveAsString(ReadableBytes());
  }

  void Append(const char *data) {
    int32_t len = strlen(data);
    EnsureWritableBytes(len);
    std::copy(data, data + len, BeginWrite());
    HasWritten(len);
  }

  void Append(const char *data, int32_t len) {
    EnsureWritableBytes(len);
    std::copy(data, data + len, BeginWrite());
    HasWritten(len);
  }

  void Append(const std::string_view &str) { Append(str.data(), str.size()); }

  void Append(const void *data, int32_t len) {
    Append(static_cast<const char *>(data), len);
  }

  void AppendInt32(int32_t x) {
    int32_t be32 = socket::HostToNetwork32(x);
    Append(&be32, sizeof be32);
  }

  void AppendInt64(int64_t x) {
    int64_t be64 = socket::HostToNetwork64(x);
    Append(&be64, sizeof be64);
  }

  void AppendInt16(int16_t x) {
    int16_t be16 = socket::HostToNetwork16(x);
    Append(&be16, sizeof be16);
  }

  void AppendInt8(int8_t x) { Append(&x, sizeof x); }

  void PrependInt64(int64_t x) {
    int64_t be64 = socket::HostToNetwork64(x);
    Prepend(&be64, sizeof be64);
  }

  void PrependInt32(int32_t x) {
    int32_t be32 = socket::HostToNetwork32(x);
    Prepend(&be32, sizeof be32);
  }

  void PrependInt16(int16_t x) {
    int16_t be16 = x;
    // socket::HostToNetwork16(x);
    Prepend(&be16, sizeof be16);
  }

  void PrependInt8(int8_t x) {
    int8_t be8 = x;
    Prepend(&be8, sizeof be8);
  }

  void Prepend(const void *data, int32_t len) {
    assert(len <= PrependableBytes());
    reader_index_ -= len;
    const char *d = static_cast<const char *>(data);
    std::copy(d, d + len, Begin() + reader_index_);
  }

  void Preapend(const void *data, int32_t len) {
    Prepend(static_cast<const char *>(data), len);
  }

  void Preapend(const char *data, int32_t len) {
    EnsureWritableBytes(len);
    std::copy(PrePeek(), PrePeek() + WritableBytes(), PrePeek() + len);
    std::copy(data, data + len, PrePeek());
    HasWritten(len);
  }

  void EnsureWritableBytes(int32_t len) {
    if (WritableBytes() < len) {
      MakeSpace(len);
    }
    assert(WritableBytes() >= len);
  }

  char *BeginWrite() { return Begin() + writer_index_; }

  const char *BeginWrite() const { return Begin() + writer_index_; }

  void HasWritten(int32_t len) {
    assert(len <= WritableBytes());
    writer_index_ += len;
  }

  void UnWrite(int32_t len) {
    assert(len <= ReadableBytes());
    writer_index_ -= len;
  }

  int64_t ReadInt64() {
    int64_t result = PeekInt64();
    RetrieveInt64();
    return result;
  }

  int32_t ReadInt32() {
    int32_t result = PeekInt32();
    RetrieveInt32();
    return result;
  }

  int16_t ReadInt16() {
    int16_t result = PeekInt16();
    RetrieveInt16();
    return result;
  }

  int8_t ReadInt8() {
    int8_t result = PeekInt8();
    RetrieveInt8();
    return result;
  }

  int64_t PeekInt64() const {
    assert(ReadableBytes() >= sizeof(int64_t));
    int64_t be64 = 0;
    ::memcpy(&be64, Peek(), sizeof be64);
    return socket::NetworkToHost64(be64);
  }

  int32_t PeekInt32() const {
    assert(ReadableBytes() >= sizeof(int32_t));
    int32_t be32 = 0;
    ::memcpy(&be32, Peek(), sizeof be32);
    return socket::NetworkToHost32(be32);
  }

  int16_t PeekInt16() const {
    assert(ReadableBytes() >= sizeof(int16_t));
    int16_t be16 = 0;
    ::memcpy(&be16, Peek(), sizeof be16);
    return socket::NetworkToHost16(be16);
  }

  int8_t PeekInt8() const {
    assert(ReadableBytes() >= sizeof(int8_t));
    int8_t be8 = *Peek();
    return be8;
  }

  std::string_view ToStringView() const {
    return std::string_view(Peek(), static_cast<int32_t>(ReadableBytes()));
  }

  void Shrink(int32_t reserve) {
    Buffer other;
    other.EnsureWritableBytes(ReadableBytes() + reserve);
    other.Append(ToStringView());
    Swap(other);
  }

  int32_t InternalCapacity() const { return buffer_.capacity(); }

  ssize_t ReadFd(int32_t fd, int32_t *saved_errno);

 private:
  Buffer(const Buffer &);

  void operator=(const Buffer &);

  char *Begin() { return &*buffer_.begin(); }

  char *PrePeek() { return Begin() + reader_index_; }

  const char *Begin() const { return &*buffer_.begin(); }

  void MakeSpace(int32_t len) {
    if (WritableBytes() + PrependableBytes() < len + kCheapPrepend) {
      buffer_.resize(writer_index_ + len);
    } else {
      assert(kCheapPrepend < reader_index_);
      int32_t readable = ReadableBytes();
      std::copy(Begin() + reader_index_, Begin() + writer_index_,
                Begin() + kCheapPrepend);
      reader_index_ = kCheapPrepend;
      writer_index_ = reader_index_ + readable;
      assert(readable == ReadableBytes());
    }
  }

 private:
  std::vector<char> buffer_;
  int32_t reader_index_;
  int32_t writer_index_;

  static const char kCRLF[];
  static const char kCRLFCRLF[];
  static const char CONTENT[];
};
}  // namespace zrpc